/* globals.cpp - source for Global.
 */

#include "common.h"
#include "error.h"

#if 0

Global::OptionBag   Global::Options =
    {
    -1,                 // ExpectCode
    -1,                 // ExpectLine
    0,                  // TabStop
    0,                  // VerboseFlag
    false,              // TokenizeFlag
    nullptr,            // ShowFirst
    nullptr,            // ShowFollow
    nullptr,            // StyleFile
    };

int          Global::TabStop;
bool         Global::TokenizeFlag;
const char*  Global::InputFile;
const char*  Global::ShowFirst;
const char*  Global::ShowFollow;
const char*  Global::StyleFile;

#endif

int         Global::Dump;
string      Global::ExpectCode;
int         Global::ExpectLine;
string      Global::InputFile;
string      Global::Locale;
string      Global::LocaleDir;
int         Global::Verbose;

vector<TOptionBag> Global::OptionStack;

struct TOption;
typedef void    (*SETOPTIONFUNC)(TOption* Option, const char* Arg);

struct TOption
    {
    TOption(const char* Name_) : Name(Name_), IsSet(false) {};
    const char*     Name;
    bool            IsSet;
    virtual void    SetToDefault()  = 0;
    virtual void    Set(const char*)= 0;
    virtual void    UnSet(const char*)= 0;
    void            RequireArg(const char* Arg, bool Require);
    };

void TOption::RequireArg(const char* Arg, bool Require)
    {
    const char* ArgArg = strchr(Arg, '=');

    if(ArgArg && !Require)
        Error("OPTION_UNEXPECTED_ARG",
            "Option", Arg,
            "OptionName", Name
            );
    else if(!ArgArg && Require)
        Error("OPTION_MISSING_ARG",
            "Option", Arg,
            "OptionName", Name
            );

    }

struct TOptionIntFlag : public TOption
    {
    TOptionIntFlag(const char* Name_, int* OptPtr_) : TOption(Name_), OptPtr(OptPtr_) {};
    int*            OptPtr;
    virtual void    SetToDefault() { *OptPtr = 0; };
    virtual void    Set(const char* Arg) { RequireArg(Arg, false); ++*OptPtr; }
    virtual void    UnSet(const char* Arg) { RequireArg(Arg, false); --*OptPtr; }
    };

struct TOptionStr : public TOption
    {
    TOptionStr(const char* Name_, string* StrPtr_, const char*Default_="")
        : TOption(Name_), StrPtr(StrPtr_), Default(Default_) {};
    const char*     Default;
    string*         StrPtr;
    virtual void    SetToDefault() { *StrPtr=Default; }
    virtual void    Set(const char* Arg) { RequireArg(Arg, true); *StrPtr = strchr(Arg,'=')+1; }
    virtual void    UnSet(const char* Arg) { RequireArg(Arg, false); SetToDefault(); }
    };


//static const TOption OptionNames[] =
std::array<unique_ptr<TOption>,4> OptionNames
    {
    std::make_unique<TOptionIntFlag>("Dump", &Global::Dump),
    std::make_unique<TOptionIntFlag>("Verbose", &Global::Verbose),
    std::make_unique<TOptionStr>("Locale", &Global::Locale, "eng"),
    std::make_unique<TOptionStr>("LocaleDir", &Global::LocaleDir),
#if 0
    "Dump",             TOption::Flag,      0,
    "ExpectCode",       TOption::String,    0,
    "ExpectLine",       TOption::Integer,   0,
    "First",            TOption::String,    0,
    "folloW",           TOption::String,    0,
    "Locale",           TOption::String,    0,
    "localeDir",        TOption::Dir,       0,
    "Tabstop",          TOption::TabStop,   0,
    "Verbose",          TOption::Flag,      0,
    "VeryVerbose",      TOption::Flag,      0,
#endif
    };

/* ScoreMatch() - produce a ranking score from a pattern and a string.
 *
 * The pattern is from OptionNames, the target string is a command-line option
 * supplied by the user.
 */
static
int     ScoreMatch(const char* Pattern, const char* Str)
    {
    int     Score   = -1;

    while(*Str && *Str != '=' && *Pattern)
        {
        // if it's an 'unimportant' pattern character
        if(islower(*Pattern))
            {
            // check for pattern this algorithm won't work with!
            assert(strchr(Pattern+1, *Pattern^' ') == nullptr);

            if(*Pattern == (*Str|' '))
                ++Str, ++Score;         // one point for matching unimportant char
            ++Pattern;                  // skip it whether it matched or not
            }
        // else if it's a required pattern character
        else if(isupper(*Pattern))
            {
            if((*Pattern|' ') == (*Str|' '))
                ++Pattern, ++Str, Score += 5;
            else
                break;
            }
        else
            assert(false);
        }
    // if we failed to match all the chars user supplied
    if(*Str)
        Score   = -1;
    return Score;
    }

void ParseOption(const char* Arg, bool TurnOptionOff)
    {
    int  BestScore = -1, BestOption = -1, AmbigOption = -1;
    for(size_t iOption=0; iOption < OptionNames.size(); ++iOption)
        {
        auto&    Option = OptionNames[iOption];
        int     Score   = ScoreMatch(Option->Name, Arg);

        if(Score == BestScore)
            AmbigOption = iOption;
        else if(Score > BestScore)
            {
            AmbigOption = -1;
            BestScore   = Score;
            BestOption  = iOption;
            }
        }
    if(BestScore < 0)
        Error("OPTION_UNKNOWN", "Option", Arg);
    else if(AmbigOption >= 0)
        Error("OPTION_AMBIGUOUS",
            "Option", Arg,
            "Choice1", OptionNames[BestOption]->Name,
            "Choice2", OptionNames[AmbigOption]->Name
            );
    else if(TurnOptionOff)
        OptionNames[BestOption]->UnSet(Arg);
    else
        OptionNames[BestOption]->Set(Arg);
    }

/* ParseArgs() - parse command-line arguments.
 *
 * Each time we get invoked, it represents options of a lower precedence,
 * which therefore get stored at the end of the option stack.
 */
void Global::ParseArgs(int ArgCount, const char*const* Args, bool Reset)
    {
    // caller ask for reset whenever starting a new run
    if(Reset)
        for(auto& Option : OptionNames)
            {
            // if this assert fails, you didn't provide enough initializers for the array
            assert(Option.get() != nullptr);
            Option->SetToDefault();
            }

    // for each command-line argument
    for(int iArg = 1; iArg < ArgCount; ++iArg)
        {
        auto Arg     = Args[iArg];
        if(Arg[0] == '-' && Arg[1] != '\0')   // if it's an option
            {
            bool    TurnOptionOff = false;
            ++Arg;
            if(Arg[0] == '-')
                {
                TurnOptionOff   = true;
                ++Arg;
                }
            ParseOption(Arg, TurnOptionOff);
            }
        else    // else, not an option
            {
            if(Global::InputFile.empty())
                Global::InputFile   = Arg;
            else
                Error("FILENAME_ALREADY_SPECIFIED",
                    "Arg", Arg,
                    "Previous", Global::InputFile
                );
            }
        }
    if(Global::Locale.empty())
        Global::Locale  = "eng";
    }


#if 0
const char* Global::GetOpt(const char* Name)
    {
    const char* Result = nullptr;

    for(auto Option: OptionStack)
        {
        if(Option.count(Name))
            {
            }
        }
    return Result;
    }

int     Global::GetIntArg(int Char, const char* Arg)
    {
//    if(!ISDIGIT(*Arg))
//        ErrorExit(ERROR_COMMANDLINE, "Expecting integer after -%c.\n", Char);
    return atoi(Arg);
    }

#endif
